﻿/*
 * Copyright 2012 Nascent Digital (http://www.nascentdigital.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using Nascent.GoogleAnalytics.Metrics;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using Windows.Foundation.Collections;
using Windows.Storage;


namespace Nascent.GoogleAnalytics
{
    /// <summary>
    /// Represents persisted session-specific information and context that 
    /// would typically be represented using cookies in the standard Google
    /// Analytics JavaScript library.
    /// </summary>
    internal sealed class AnalyticsSession
    {
        #region constants

        private const int SessionTimeoutSeconds = 30;
        private const string CustomVariablesKey = "customVariables";
        private const string VisitorIdKey = "visitorId";
        private const string FirstStartTimeKey = "firstStart";
        private const string LastStartTimeKey = "lastStart";
        private const string StartTimeKey = "start";
        private const string CountKey = "count";
        private const string TimestampKey = "timestamp";

        #endregion


        #region class variables

        private static readonly TimeSpan _timeoutInterval;

        #endregion


        #region instance variables

        private readonly ApplicationDataContainer _sessionContainer;
        private readonly IDictionary<int, Variable> _variables;
        private readonly string _webPropertyId;

        #endregion


        #region constructors

        static AnalyticsSession()
        {
            // initialize class variables
            _timeoutInterval = TimeSpan.FromSeconds(SessionTimeoutSeconds);
        }

        private AnalyticsSession(ApplicationDataContainer sessionContainer,
            string webPropertyId)
        {
            // initialize instance variables
            _sessionContainer = sessionContainer;
            _variables = new Dictionary<int, Variable>();
            _webPropertyId = webPropertyId;

            // hydrate from settings (or default if value not found)
            IPropertySet sessionContainerValues = sessionContainer.Values;
            object value;
            if (sessionContainerValues.TryGetValue(CustomVariablesKey, out value))
            {
                // read variables
                try
                {
                    // deserialize variables
                    string serializedVariables = (string)value;
                    DataContractSerializer serializer = new DataContractSerializer(
                        typeof(Variable[]), new Type[]
                        {
                            typeof(Variable),
                            typeof(VariableScope)
                        });
                    Variable[] variables = (Variable[])
                        serializer.ReadObject(XmlReader.Create(
                            new StringReader(serializedVariables)));

                    // merge into custom variables
                    foreach (Variable variable in variables)
                    {
                        _variables[variable.Index] = variable;
                    }
                }

                // or clear value
                catch (Exception)
                {
                    sessionContainerValues.Remove(CustomVariablesKey);
                }
            }

            if (sessionContainerValues.TryGetValue(VisitorIdKey, out value))
            {
                VisitorId = (int)value;
            }
            else
            {
                VisitorId = Math.Abs(Guid.NewGuid().GetHashCode());
            }

            if (sessionContainerValues.TryGetValue(FirstStartTimeKey, 
                out value))
            {
                FirstStartTime = (long)value;
            }
            else
            {
                FirstStartTime = DateTime.UtcNow.ToUnixTimestamp();
            }

            if (sessionContainerValues.TryGetValue(LastStartTimeKey, 
                out value))
            {
                LastStartTime = (long)value;
            }
            else
            {
                LastStartTime = FirstStartTime;
            }

            if (sessionContainerValues.TryGetValue(StartTimeKey, out value))
            {
                CurrentStartTime = (long)value;
            }
            else
            {
                CurrentStartTime = FirstStartTime;
            }

            if (sessionContainerValues.TryGetValue(CountKey, out value))
            {
                Count = (int)value;
            }
            else
            {
                Count = 1;
            }

            if (sessionContainerValues.TryGetValue(TimestampKey, out value))
            {
                long timestamp = (long)value;
                Timestamp = timestamp.ToDateTime();
            }
            else
            {
                Timestamp = DateTime.MinValue;
            }
        }

        #endregion


        #region properties

        public IDictionary<int, Variable> Variables
        {
            get { return _variables; }
        }

        public string WebPropertyId 
        { 
            get { return _webPropertyId; } 
        }

        public int VisitorId { get; set; }

        public long FirstStartTime { get; set; }

        public long LastStartTime { get; set; }

        public long CurrentStartTime { get; set; }

        public int Count { get; set; }

        public DateTime Timestamp { get; set; }

        #endregion


        #region public methods

        public static AnalyticsSession Load(string webPropertyId)
        {
            // determine container name
            string sessionContainerName = string.Format(
                CultureInfo.InvariantCulture, "_nd_ga_{0}",
                webPropertyId.Replace('-', '_').ToLowerInvariant());

            // open/create session container 
            ApplicationDataContainer localSettings = ApplicationData.Current
                .LocalSettings;
            ApplicationDataContainer sessionContainer = localSettings
                .CreateContainer(sessionContainerName,
                ApplicationDataCreateDisposition.Always);

            // create session
            AnalyticsSession session = new AnalyticsSession(sessionContainer, 
                webPropertyId);

            // return session
            return session;
        }

        public void Update(DateTime timestamp)
        {
            // copy timestamp if never set
            if (Timestamp == DateTime.MinValue)
            {
                Timestamp = timestamp;
            }

            // increment session if timeout occurred
            TimeSpan updateInterval = timestamp - Timestamp;
            IPropertySet sessionContainerValues = _sessionContainer.Values;
            if (updateInterval > _timeoutInterval)
            {
                // update session values
                LastStartTime = CurrentStartTime;
                CurrentStartTime = timestamp.ToUnixTimestamp();
                Count = Count + 1;
                Timestamp = timestamp;

                // persist session
                sessionContainerValues[VisitorIdKey] = VisitorId;
                sessionContainerValues[FirstStartTimeKey] = FirstStartTime;
                sessionContainerValues[LastStartTimeKey] =
                    LastStartTime;
                sessionContainerValues[StartTimeKey] =
                    CurrentStartTime;
                sessionContainerValues[CountKey] = Count;
                sessionContainerValues[TimestampKey] = Timestamp
                    .ToUnixTimestamp();

                // remove any page or session scoped variables
                bool variablesUpdated = false;
                foreach (Variable variable 
                    in _variables.Values.ToArray())
                {
                    if (variable.Scope != VariableScope.Visitor)
                    {
                        // remove variable
                        _variables.Remove(variable.Index);

                        // mark update
                        variablesUpdated = true;
                    }
                }

                // update variable if changed
                if (variablesUpdated)
                {
                    UpdateVariables();
                }
            }

            // otherwise, update timestamp
            else
            {
                // update session value
                Timestamp = timestamp;

                // persist session
                sessionContainerValues[TimestampKey] = Timestamp
                    .ToUnixTimestamp();
            }
        }

        public void UpdateVariables()
        {
            // capture session and vistor scoped variables
            Variable[] variables = _variables.Select(e => e.Value)
                .Where(c => c.Scope != VariableScope.Page)
                .ToArray();

            // save variables (if any)
            IPropertySet sessionContainerValues = _sessionContainer.Values;
            if (variables.Length > 0)
            {
                // serialize variables
                StringBuilder serializedVariables = new StringBuilder(512);
                using (XmlWriter writer = XmlWriter.Create(serializedVariables))
                {
                    // create serializer
                    DataContractSerializer serializer = new DataContractSerializer(
                        typeof(Variable[]), new Type[]
                            {
                                typeof(Variable),
                                typeof(VariableScope)
                            });

                    // serialize values
                    serializer.WriteObject(writer, variables);
                }

                // persist variables
                sessionContainerValues[CustomVariablesKey] = 
                    serializedVariables.ToString();
            }

            // or clear entry
            else
            {
                sessionContainerValues.Remove(CustomVariablesKey);
            }
        }

        #endregion

    }  // class AnalyticsSession

}  // namespace Nascent.GoogleAnalytics
